package com.ctos.ojcodesandbox;

import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.ctos.ojcodesandbox.model.ExecuteCodeRequest;
import com.ctos.ojcodesandbox.model.ExecuteCodeResponse;
import com.ctos.ojcodesandbox.model.ExecuteMessage;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.PullImageResultCallback;
import com.github.dockerjava.api.command.StatsCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class JavaDockerCodeSandbox extends JavaCodeSandboxTemplate {


    private static final Long TIME_OUT  = 5L;

    public static final String IMAGE = "openjdk:8-alpine";

    public static void main(String[] args) {
        JavaDockerCodeSandbox javaDockerCodeSandbox = new JavaDockerCodeSandbox();
        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputList(Arrays.asList("1 2", "1 3"));
       // String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
        String code = ResourceUtil.readStr("testCode/simpleComputeArgs/Main.java", StandardCharsets.UTF_8);
       //  String code = ResourceUtil.readStr("testCode/unsafeCode/SleepError.java", StandardCharsets.UTF_8);
       // String code = ResourceUtil.readStr("testCode/simpleCompute/Main.java", StandardCharsets.UTF_8);
        executeCodeRequest.setCode(code);
        executeCodeRequest.setLanguage("java");
        ExecuteCodeResponse executeCodeResponse = javaDockerCodeSandbox.executeCode(executeCodeRequest);
        System.out.println(executeCodeResponse);
        // 在executeCode方法中添加

    }

    @Override
    public List<ExecuteMessage> runCode(List<String> inputList, File usercodeFile) throws IOException {
        //创建容器 执行代码
        String userCodeParentPath = usercodeFile.getParentFile().getAbsolutePath();

        DockerClient dockerClient = DockerClientBuilder.getInstance().build();
        List<Image> Images = dockerClient.listImagesCmd().exec();
        boolean hasImage = Images.stream().allMatch(image -> {
            String[] repoTags = image.getRepoTags();
            String tags = Arrays.toString(repoTags);
            return !StrUtil.contains(tags, "openjdk:8-alpine");
        });
            if (hasImage) {
//                拉取镜像
                try {
                    dockerClient.pullImageCmd(IMAGE).exec(new PullImageResultCallback() {
                        @Override
                        public void onNext(PullResponseItem item) {
                            System.out.println("下载镜像:"+item.getStatus());
                            super.onNext(item);
                        }
                    }).awaitCompletion();
                } catch (InterruptedException e) {
                    System.out.println("拉取镜像失败");
                    throw new RuntimeException(e);
                }
            }



        //指定容器文件映射
        HostConfig hostConfig = new HostConfig();
        hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));
        hostConfig.withMemory(100*1000*1000L);
        hostConfig.withMemorySwap(0L);

        //创建容器
        CreateContainerResponse exec = dockerClient.createContainerCmd(IMAGE)
                .withAttachStderr(true)
                .withAttachStdin(true)
                //禁用网络
                .withNetworkDisabled(true)
                .withAttachStdout(true)
                .withTty(true)
                .withHostConfig(hostConfig)
                .exec();
        String containerId = exec.getId();
        System.out.println("容器id:"+containerId);
        //启动容器
        dockerClient.startContainerCmd(containerId).exec();

        //创建命令
        //docker exec java -cp /app Main 1 3
        ArrayList<ExecuteMessage> executeMessageList = new ArrayList<>();
        for (String input : inputList) {
            String[] inputArgs = input.split(" ");
            String[] cmd = ArrayUtil.append(new String[]{"java","-cp","/app","Main"},inputArgs);
            ExecCreateCmdResponse exeRes = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmd)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();
            System.out.println("执行命令"+exeRes);
            String exeId = exeRes.getId();

            ExecuteMessage executeMessage = new ExecuteMessage();
            final String[] errMessage = {null};
            final String[] Message = {null};
            StopWatch stopWatch = new StopWatch();
            final Boolean[] isTimeOut = {true};
            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback(){
                @Override
                public void onNext(Frame frame) {
                    StreamType streamType = frame.getStreamType();
                    if (StreamType.STDERR.equals(streamType)){
                        errMessage[0] = new String(frame.getPayload()).replace("\n", "");
                        System.out.println("执行错误"+errMessage[0]);
                    }
                    Message[0] = new String(frame.getPayload()).replace("\n", "");
                    super.onNext(frame);
                }

                @Override
                public void onComplete() {
                    isTimeOut[0] =false;
                    super.onComplete();
                }
            };

            // 获取占用的内存
            Long maxMemory[] = {0L};
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);
            ResultCallback<Statistics> statisticsResultCallback = statsCmd.exec(new ResultCallback<Statistics>() {

                @Override
                public void onNext(Statistics statistics) {
                    System.out.println("内存占用：" + statistics.getMemoryStats().getUsage());
                    maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
                }

                @Override
                public void close() throws IOException {

                }

                @Override
                public void onStart(Closeable closeable) {

                }

                @Override
                public void onError(Throwable throwable) {

                }

                @Override
                public void onComplete() {

                }
            });
            statsCmd.exec(statisticsResultCallback);


            //执行命令
            try {
                stopWatch.start();
                dockerClient
                        .execStartCmd(exeId)
                        .exec(execStartResultCallback)
                        .awaitCompletion(TIME_OUT,TimeUnit.SECONDS);
                stopWatch.stop();

            } catch (InterruptedException e) {
                System.out.println("执行命令失败");
            }finally {
                statsCmd.close();
            }

            //判断是否超时
            if (isTimeOut[0]){
                try {
                    dockerClient.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                throw new RuntimeException("代码执行时间过长");
            }
            executeMessage.setTime(stopWatch.getLastTaskTimeMillis());
            executeMessage.setErrorMessage(errMessage[0]);
            executeMessage.setMessage(Message[0]);
            executeMessage.setMemory(maxMemory[0]);
            executeMessageList.add(executeMessage);
        }
        //清理容器
        dockerClient
                .removeContainerCmd(containerId)
                .withForce(true)
                .exec();
        return executeMessageList;
    }



}


